' 
' Copyright (C) 2014 Infinity CCS Ltd (http://www.infinityccs.com/)
' Copyright (C) 2008 Sasa Coh <sasacoh@gmail.com>
'
' This program is free software; you can redistribute it and/or modify
' it under the terms of the GNU General Public License as published by
' the Free Software Foundation; either version 3 of the License, or
' (at your option) any later version.
'
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY; without even the implied warranty of
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
' GNU General Public License for more details.
'
' You should have received a copy of the GNU General Public License
' along with this program.  If not, see <http://www.gnu.org/licenses/>. 
'/


Imports System
Imports System.Text
Imports Infinity.pjsipNET.Common
Imports System.Runtime.InteropServices
Imports Infinity.pjsipNET.Common.Config

''' <summary>
''' Implementation of ICallProxyInterface interface use by call state machine. 
''' Each call (session) contains an instance of a call proxy. 
''' Current session is identified by SessionId property.
''' pjsipCallProxy communicates with pjsip stack using API functions and callbacks.
''' </summary>
Friend Class pjsipCallProxy
  Inherits ICallProxy

#Region "DLL declarations"
  '// call API
  <DllImport("Infinity.pjsip.dll", EntryPoint:="dll_makeCall", CallingConvention:=CallingConvention.Cdecl)>
  Private Shared Function dll_makeCall(accountId As Integer, uri As String) As Integer

  End Function
  <DllImport("Infinity.pjsip.dll", EntryPoint:="dll_releaseCall", CallingConvention:=CallingConvention.Cdecl)>
  Private Shared Function dll_releaseCall(callId As Integer) As Integer

  End Function
  <DllImport("Infinity.pjsip.dll", EntryPoint:="dll_answerCall", CallingConvention:=CallingConvention.Cdecl)>
  Private Shared Function dll_answerCall(callId As Integer, code As Integer) As Integer

  End Function
  <DllImport("Infinity.pjsip.dll", EntryPoint:="dll_holdCall", CallingConvention:=CallingConvention.Cdecl)>
  Private Shared Function dll_holdCall(callId As Integer) As Integer

  End Function
  <DllImport("Infinity.pjsip.dll", EntryPoint:="dll_retrieveCall", CallingConvention:=CallingConvention.Cdecl)>
  Private Shared Function dll_retrieveCall(callId As Integer) As Integer

  End Function
  <DllImport("Infinity.pjsip.dll", EntryPoint:="dll_xferCall", CallingConvention:=CallingConvention.Cdecl)>
  Private Shared Function dll_xferCall(callId As Integer, uri As String) As Integer

  End Function
  <DllImport("Infinity.pjsip.dll", EntryPoint:="dll_xferCallWithReplaces", CallingConvention:=CallingConvention.Cdecl)>
  Private Shared Function dll_xferCallWithReplaces(callId As Integer, dstSession As Integer) As Integer

  End Function
  <DllImport("Infinity.pjsip.dll", EntryPoint:="dll_serviceReq", CallingConvention:=CallingConvention.Cdecl)>
  Private Shared Function dll_serviceReq(callId As Integer, serviceCode As Integer, destUri As String) As Integer

  End Function
  <DllImport("Infinity.pjsip.dll", EntryPoint:="dll_dialDtmf", CallingConvention:=CallingConvention.Cdecl)>
  Private Shared Function dll_dialDtmf(callId As Integer, digits As String, mode As Integer) As Integer

  End Function
  <DllImport("Infinity.pjsip.dll", EntryPoint:="dll_getCurrentCodec", CallingConvention:=CallingConvention.Cdecl)>
  Private Shared Function dll_getCurrentCodec(callId As Integer, codec As StringBuilder) As Integer

  End Function
  <DllImport("Infinity.pjsip.dll", EntryPoint:="dll_makeConference", CallingConvention:=CallingConvention.Cdecl)>
  Private Shared Function dll_makeConference(callId As Integer) As Integer

  End Function
  '<DllImport("Infinity.pjsip.dll", EntryPoint:="dll_sendCallMessage", CallingConvention:=CallingConvention.Cdecl)>
  'Private Shared Function dll_sendCallMessage(callId As Integer, message As String) As Integer

  'End Function

#End Region

#Region "Callback Declarations"
  '// passing delegates to unmanaged code (.dll)
  <DllImport("Infinity.pjsip.dll", CallingConvention:=CallingConvention.Cdecl)>
  Private Shared Function setCallback_onCallState(cb As delOnCallStateChanged) As Integer
  End Function

  <DllImport("Infinity.pjsip.dll", CallingConvention:=CallingConvention.Cdecl)>
  Private Shared Function setCallback_onCallIncoming(cb As delOnCallIncoming) As Integer
  End Function

  <DllImport("Infinity.pjsip.dll", CallingConvention:=CallingConvention.Cdecl)>
  Private Shared Function setCallback_onCallHoldConfirm(cb As delOnCallHoldConfirm) As Integer
  End Function

  '// callback delegates
  Delegate Function delOnCallStateChanged(callId As Integer, stateId As CallState.ESessionState, lastStatus As Integer, lastStatusText As String) As Integer
  Shared csDel As delOnCallStateChanged = New delOnCallStateChanged(AddressOf onCallStateChanged)

  Delegate Function delOnCallIncoming(accountId As Integer, callId As Integer, stsourceuri As String, stdesturi As String) As Integer
  Shared ciDel As delOnCallIncoming = New delOnCallIncoming(AddressOf onCallIncoming)

  Delegate Function delOnCallHoldConfirm(callId As Integer) As Integer
  Shared chDel As delOnCallHoldConfirm = New delOnCallHoldConfirm(AddressOf onCallHoldConfirm)

#End Region

#Region "Properties"
  Private _config As IConfigurator = New NullConfigurator()

  Private ReadOnly Property Config As IConfigurator
    Get
      Return _config
    End Get
  End Property

  Private _sessionId As Integer
  Public Overrides Property SessionId As Integer
    Get
      Return _sessionId
    End Get
    Set(value As Integer)
      _sessionId = value
    End Set
  End Property

  Private _accountId As Integer
  Public Overrides Property AccountId As Integer
    Get
      Return _accountId
    End Get
    Set(value As Integer)
      _accountId = value
    End Set
  End Property
#End Region

#Region "Constructor"

  ''' <summary>
  ''' Constructor called by pjsipWrapper with Config parameter. 
  ''' Make sure you set Config in pjsipWrapper before using pjsipCallProxy
  ''' </summary>
  ''' <param name="config"></param>
  Friend Sub New(config As IConfigurator)
    _config = config
  End Sub

  ''' <summary>
  ''' Static initializer. Call this method to set callbacks from SIP stack. 
  ''' </summary>
  Public Shared Sub initialize()
    setCallback_onCallIncoming(ciDel)
    setCallback_onCallState(csDel)
    setCallback_onCallHoldConfirm(chDel)
  End Sub


#End Region 'Constructor

#Region "Public Methods"

  ''' <summary>
  ''' Method makeCall creates call session. Checks the 1st parameter 
  ''' format is SIP URI, if not build one.  
  ''' </summary>
  ''' <param name="dialedNo"></param>
  ''' <returns>SessionId chosen by pjsip stack</returns>
  Public Overrides Function makeCall(dialedNo As String, accountKey As String) As Integer
    Dim sipuri As String = ""

    '// check if call by URI
    If (dialedNo.IndexOf("sip:") = 0) Then
      '// do nothing...
      sipuri = dialedNo
    Else
      '// prepare URI
      sipuri = "sip:" + dialedNo + "@" + Config.Account(accountKey).HostName
    End If
    '// Select configured transport for this account: udp, tcp, tls 
    sipuri = pjsipStackProxy.Instance.SetTransport(accountKey, sipuri)

    '// Don't forget to convert accontId here!!!
    '// Store session identification for further requests
    SessionId = dll_makeCall(Config.Account(accountKey).pjsipIndex, sipuri)

    Return SessionId
  End Function

  ''' <summary>
  ''' End call for a given session
  ''' </summary>
  ''' <returns></returns>
  Public Overrides Function endCall() As Boolean
    dll_releaseCall(SessionId)
    Return True
  End Function

  ''' <summary>
  ''' Signals sip stack that device is alerted (ringing)
  ''' </summary>
  ''' <returns></returns>
  Public Overrides Function alerted() As Boolean
    dll_answerCall(SessionId, 180)
    Return True
  End Function

  ''' <summary>
  ''' Signals that user accepts the call (asnwer)
  ''' </summary>
  ''' <returns></returns>
  Public Overrides Function acceptCall() As Boolean
    dll_answerCall(SessionId, 200)
    Return True
  End Function

  ''' <summary>
  ''' Hold request for a given session
  ''' </summary>
  ''' <returns></returns>
  Public Overrides Function holdCall() As Boolean
    dll_holdCall(SessionId)
    Return True
  End Function

  ''' <summary>
  ''' Retrieve request for a given session
  ''' </summary>
  ''' <returns></returns>
  Public Overrides Function retrieveCall() As Boolean
    dll_retrieveCall(SessionId)
    Return True
  End Function

  ''' <summary>
  ''' Trasfer call to number
  ''' </summary>
  ''' <param name="number"></param>
  ''' <returns></returns>
  Public Overrides Function xferCall(number As String) As Boolean
    Dim uri As String = "sip:" + number + "@" + Config.Account(AccountId).HostName
    dll_xferCall(SessionId, uri)
    Return True
  End Function

  ''' <summary>
  ''' Transfer call to other session
  ''' </summary>
  ''' <param name="session"></param>
  ''' <returns></returns>
  Public Overrides Function xferCallSession(session As Integer) As Boolean
    dll_xferCallWithReplaces(SessionId, session)
    Return True
  End Function

  ''' <summary>
  ''' Make conference with given session
  ''' </summary>
  ''' <param name="session"></param>
  ''' <returns></returns>
  Public Overrides Function threePtyCall(session As Integer) As Boolean
    dll_serviceReq(SessionId, CInt(EServiceCodes.SC_3PTY), "")
    Return True
  End Function

  ''' <summary>
  ''' 
  ''' </summary>
  ''' <param name="code"></param>
  ''' <param name="dest"></param>
  ''' <returns></returns>
  Public Overrides Function serviceRequest(code As Integer, dest As String) As Boolean
    Dim destUri As String = "<sip:" + dest + "@" + Config.Account(AccountId).HostName + ">"
    dll_serviceReq(SessionId, code, destUri)
    Return True
  End Function

  ''' <summary>
  ''' Send dtmf digit
  ''' </summary>
  ''' <param name="digits"></param>
  ''' <param name="mode"></param>
  ''' <returns></returns>
  Public Overrides Function dialDtmf(digits As String, mode As EDtmfMode) As Boolean
    Dim status As Integer = dll_dialDtmf(SessionId, digits, CInt(mode))
    Return True
  End Function

  ''' <summary>
  ''' 
  ''' </summary>
  ''' <returns></returns>
  Public Overrides Function getCurrentCodec() As String
    Dim codec As StringBuilder = New StringBuilder(256)
    Dim status As Integer = dll_getCurrentCodec(SessionId, codec)
    Return codec.ToString()
  End Function

  ''' <summary>
  ''' Make a conference call
  ''' </summary>
  ''' <returns></returns>
  Public Overrides Function conferenceCall() As Boolean
    Dim status As Integer = dll_makeConference(SessionId)
    Return If(status = 1, True, False)
  End Function

#End Region 'Methods

#Region "Callbacks"

  ''' <summary>
  ''' 
  ''' </summary>
  ''' <param name="callId"></param>
  ''' <param name="callState"></param>
  ''' <returns></returns>
  Private Shared Function onCallStateChanged(callId As Integer, callState As CallState.ESessionState, lastStatus As Integer, lastStatusText As String) As Integer
    ICallProxy.BaseCallStateChanged(callId, callState, "", lastStatus, lastStatusText)
    Return 0
  End Function

  ''' <summary>
  ''' 
  ''' </summary>
  ''' <param name="callId"></param>
  ''' <param name="stsourceuri"></param>
  ''' <param name="stdesturi"></param>
  ''' <returns></returns>
  Private Shared Function onCallIncoming(accountId As Integer, callId As Integer, stsourceuri As String, stdesturi As String) As Integer

    Dim clidisplay As String = ""
    Dim number As String = ""
    Dim ddi As String = ""

    If stsourceuri IsNot Nothing Then
      Dim uri As String = stsourceuri
      '// get indices
      Dim startNum As Integer = uri.IndexOf("<sip:")
      Dim atPos As Integer = uri.IndexOf("@")
      '// search for number
      If ((startNum >= 0) AndAlso (atPos > startNum)) Then
        number = uri.Substring(startNum + 5, atPos - startNum - 5)
      End If

      '// extract display name if exists
      If (startNum >= 0) Then
        clidisplay = uri.Remove(startNum, uri.Length - startNum).Trim()
      Else
        Dim semiPos As Integer = clidisplay.IndexOf(";")
        If (semiPos >= 0) Then
          clidisplay = clidisplay.Remove(semiPos, clidisplay.Length - semiPos)
        Else
          Dim colPos As Integer = clidisplay.IndexOf(":")
          If (colPos >= 0) Then
            clidisplay = clidisplay.Remove(colPos, clidisplay.Length - colPos)
          End If
        End If
      End If
    End If

    If stdesturi IsNot Nothing Then
      Dim uri As String = stdesturi
      '// get indices
      Dim startNum As Integer = uri.IndexOf("<sip:")
      Dim atPos As Integer = uri.IndexOf("@")
      '// search for number
      If ((startNum >= 0) AndAlso (atPos > startNum)) Then
        ddi = uri.Substring(startNum + 5, atPos - startNum - 5)
      End If
    End If

    '// invoke callback
    ICallProxy.BaseIncomingCall(accountId, callId, number, ddi, clidisplay)
    ICallProxy.BaseCallStateChanged(callId, CallState.ESessionState.SESSION_STATE_INCOMING, "", 0, "")
    Return 1
  End Function

  ''' <summary>
  ''' Not used
  ''' </summary>
  ''' <param name="callId"></param>
  ''' <returns></returns>
  Private Shared Function onCallHoldConfirm(callId As Integer) As Integer
    '//if (sm != null) sm.getState().onHoldConfirm();
    '// TODO:::implement proper callback
    BaseCallNotification(callId, ECallNotification.CN_HOLDCONFIRM, "")
    Return 1
  End Function
#End Region
End Class
